<?php

namespace lib\Server;


use lib\Root;
use lib\Server\Mysql\MysqlOrm;
use lib\Server\Service\ThriftService;
use lib\Server\Service\TcpHttpService;

class TcpServer
{

	public $sock_type;

	public $config = [];

	/**
	 * 服务器启动主要方法
	 *
	 * @param array $config 服务器配置信息
	 * @param array $public_setting 服务器公共配置信息
	 */
	public function start($config, $public_setting = [])
	{
		$this->config = $config;

		if (Server::$swoole_instance) {
			$tcp_http_server = Server::$swoole_instance->addListener(
				$config['host'],
				$config['port'],
				$this->sock_type
			);
		}
		else {
			Server::$swoole_instance = new \swoole_server(
				$config['host'],
				$config['port'],
				SWOOLE_PROCESS,
				$this->sock_type
			);
			$tcp_http_server         =& Server::$swoole_instance;
			$tcp_http_server->on('Start', [$this, 'onStart']);
			$tcp_http_server->on('WorkerStart', [$this, 'onWorkerStart']);
			$tcp_http_server->on('WorkerStop', [$this, 'onWorkerStop']);
			$tcp_http_server->on('WorkerError', [$this, 'onWorkerError']);
			$tcp_http_server->on('Shutdown', [$this, 'onShutdown']);
			$tcp_http_server->on('timer', [$this, 'onTimer']);
			$tcp_http_server->on('Task', [$this, 'onTask']);
			$tcp_http_server->on('Finish', [$this, 'onFinish']);
		}

		if ('thrift' === $config['type']) {
			echo 'RpcService'.PHP_EOL;
			$handler = new ThriftService($config['container']);
		}
		else {
			echo 'TcpHttpService'.PHP_EOL;
			$handler = new TcpHttpService($config['container']);
		}

		$tcp_http_server->on('Connect', [$this, 'onConnect']);
		$tcp_http_server->on('Close', [$this, 'onClose']);
		$tcp_http_server->on('Receive', [$handler, 'handler']);
		$tcp_http_server->on('Packet', [$this, 'onPacket']);

		$tcp_http_server->set(array_merge($public_setting ?: [], $config['options'] ?: []));
	}

	/**
	 * @param $server
	 * @param $data
	 * @param $client_info
	 * $server，swoole_server对象 $data，收到的数据内容，可能是文本或者二进制内容
	 * $client_info，客户端信息包括address/port/server_socket 3项数据 服务器同时监听TCP/UDP端口时，收到TCP协议的数据会回调onReceive，收到UDP数据包回调onPacket，服务器设置的EOF或Length协议对UDP端口是不生效的，因为UDP包本身存在消息边界，不需要额外的协议处理。
	 * onPacket事件回调在1.7.18以上版本可用 如果未设置onPacket回调函数，收到UDP数据包默认会回调onReceive函数
	 */
	public function onPacket($server, $data, $client_info)
	{
	}


	/**
	 * @param \swoole_server $server
	 * @param $worker_id
	 * $worker_id是一个从0-$worker_num之间的数字，表示这个worker进程的ID
	 * $worker_id和进程PID没有任何关系
	 * 进程异常结束，如被强制kill、致命错误、core dump 时无法执行onWorkerStop回调函数
	 * 此事件在worker进程终止时发生。在此函数中可以回收worker进程申请的各类资源
	 */
	public function onWorkerStop(\swoole_server $server, $worker_id)
	{
	}


	/**
	 * @param \swoole_server $server
	 * @param int $interval 是定时器时间间隔，根据$interval的值来区分是哪个定时器触发的。这里的定时器是由$serv->addtimer来添加的，是固定间隔循环触发的。
	 * onTimer中执行时间过长，会导致下一次定时延缓触发。如设定1秒的定时器，1秒后会触发onTimer，onTimer函数用时1.5s，那么第二次触发onTimer的时间为第3秒。中间第2秒的定时器会被丢弃
	 * onTimer回调函数如果要执行一个耗时操作，最好是使用$serv->task投递到task进程池中执
	 */
	public function onTimer(\swoole_server $server, $interval)
	{
	}

	/**
	 * @param \swoole_server $server
	 * @param $fd
	 * @param $reactorId
	 * $server 是swoole_server对象
	 * $fd 是连接的文件描述符 $reactorId 来自那个reactor线程
	 * onClose回调函数如果发生了致命错误，会导致连接泄漏。
	 * 通过netstat命令会看到大量CLOSE_WAIT状态的TCP连接 无论由客户端发起close还是服务器端主动调用$serv->close()关闭连接，都会触发此事件。
	 * 因此只要连接关闭，就一定会回调此函数 1.7.7+版本以后onClose中依然可以调用connection_info方法获取到连接信息，在onClose回调函数执行完毕后才会调用close关闭TCP连接
	 */
	public function onClose(\swoole_server $server, $fd, $reactorId)
	{
	}

	/**
	 * 有新的连接进入时，在worker进程中回调
	 * @param \swoole_server$ server 是swoole_server对象
	 * @param int $fd 是连接的文件描述符，发送数据/关闭连接时需要此参数
	 * @param int $from_id 来自哪个Reactor线程
	 *
	 *    关于$fd和$from_id 详细的解释
	 *    onConnect/onClose这2个回调发生在worker进程内，而不是主进程。
	 *    UDP协议下只有onReceive事件，没有onConnect/onClose事件
	 *    dispatch_mode = 1/3
	 *
	 *    在1.7.15以上版本中，当设置dispatch_mode = 1/3时会自动去掉onConnect/onClose事件回调。原因是：
	 *
	 *    在此模式下onConnect/onReceive/onClose可能会被投递到不同的进程。连接相关的PHP对象数据，无法实现在onConnect回调初始化数据，onClose清理数据
	 *    onConnect/onReceive/onClose 3种事件可能会并发执行，可能会带来异常
	 */
	public function onConnect(\swoole_server $server, $fd, $from_id)
	{
	}

	/**
	 * @param \swoole_server $serv
	 * @param int $task_id
	 * @param int $src_worker_id
	 * @param string $data
	 * $task_id是任务ID，由swoole扩展内自动生成，用于区分不同的任务。$task_id和$src_worker_id组合起来才是全局唯一的，不同的worker进程投递的任务ID可能会有相同
	 * $src_worker_id来自于哪个worker进程
	 * $data 是任务的内容
	 *
	 * 在task_worker进程内被调用。worker进程可以使用swoole_server_task函数向task_worker进程投递新的任务。
	 * 当前的Task进程在调用onTask回调函数时会将进程状态切换为忙碌，这时将不再接收新的Task，当onTask函数返回时会将进程状态切换为空闲然后继续接收新的Task。
	 */
	public function onTask(\swoole_server $serv, $task_id, $src_worker_id, $data)
	{
	}

	/**
	 * @param \swoole_server $serv
	 * @param $task_id
	 * @param $data
	 * $task_id是任务的ID
	 * $data是任务处理的结果内容
	 *
	 * 当worker进程投递的任务在task_worker中完成时，task进程会通过swoole_server->finish()方法将任务处理的结果发送给worker进程。
	 */
	public function onFinish(\swoole_server $serv, $task_id, $data)
	{
	}

	/**
	 * @param \swoole_server $serv
	 * @param $worker_id
	 * @param $worker_pid
	 * @param $exit_code
	 * @param $signal
	 * $worker_id 是异常进程的编号
	 * $worker_pid 是异常进程的ID
	 * $exit_code 退出的状态码，范围是 1 ～255
	 * $signal 进程退出的信号
	 *
	 * 当worker/task_worker进程发生异常后会在Manager进程内回调此函数。
	 * 此函数主要用于报警和监控，一旦发现Worker进程异常退出，那么很有可能是遇到了致命错误或者进程CoreDump。通过记录日志或者发送报警的信息来提示开发者进行相应的处理。
	 */
	public function onWorkerError(\swoole_server $serv, $worker_id, $worker_pid, $exit_code, $signal)
	{
	}

	/**
	 * 在此之前Swoole Server已进行了如下操作
	 * 已关闭所有线程
	 * 已关闭所有worker进程
	 * 已close所有TCP/UDP监听端口
	 * 已关闭主Rector
	 * 强制kill进程不会回调onShutdown，如kill -9
	 * 需要使用kill -15来发送SIGTREM信号到主进程才能按照正常的流程终止
	 */
	public function onShutdown()
	{
	}


	/**
	 * Server启动在主进程的主线程回调此函数，函数原型
	 * @param $serv
	 *
	 * 在此事件之前Swoole Server已进行了如下操作
	 *
	 *    已创建了manager进程
	 *    已创建了worker子进程
	 *    已监听所有TCP/UDP端口
	 *    已监听了定时器
	 *    接下来要执行
	 *
	 *    主Reactor开始接收事件，客户端可以connect到Server
	 *    onStart回调中，仅允许echo、打印Log、修改进程名称。不得执行其他操作。onWorkerStart和onStart回调是在不同进程中并行执行的，不存在先后顺序。
	 *
	 *    可以在onStart回调中，将$serv->master_pid和$serv->manager_pid的值保存到一个文件中。这样可以编写脚本，向这两个PID发送信号来实现关闭和重启的操作。
	 *
	 *    从1.7.5+ Master进程内不再支持定时器，onMasterConnect/onMasterClose2个事件回调也彻底移除。Master进程内不再保留任何PHP的接口。
	 *
	 *    onStart事件在Master进程的主线程中被调用。
	 *
	 *    在onStart中创建的全局资源对象不能在worker进程中被使用，因为发生onStart调用时，worker进程已经创建好了。
	 *    新创建的对象在主进程内，worker进程无法访问到此内存区域。
	 *    因此全局对象创建的代码需要放置在swoole_server_start之前。
	 */
	public function onStart($serv)
	{
		Server::$_master_pid = $serv->master_pid;
		file_put_contents(Server::$_pid_file, Server::$_master_pid);
		file_put_contents(Server::$_pid_file, ',' . $serv->manager_pid, FILE_APPEND);
		self::setProcessTitle(Server::$_process_title . '_master');
	}

	/**
	 * @param $serv
	 * @param $workerId
	 * swoole1.6.11之后task_worker中也会触发onWorkerStart
	 * 发生PHP致命错误或者代码中主动调用exit时，Worker/Task进程会退出，管理进程会重新创建新的进程
	 * onWorkerStart/onStart是并发执行的，没有先后顺序
	 */
	public function onWorkerStart($serv, $workerId)
	{
		file_put_contents(Server::$_pid_file, ',' . $serv->worker_pid, FILE_APPEND);
		// 重新加载配置
		if (!$serv->taskworker) {//worker进程
			#实例化数据库连接池
			new MysqlOrm();

			self::setProcessTitle(Server::$_process_title . '_worker');
		}
		else {
			self::setProcessTitle(Server::$_process_title . '_tasker');
		}
	}

	#设置进程名称
	public static function setProcessTitle($name)
	{
		@swoole_set_process_name($name);
	}
}